﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;

namespace Noris.Schedule.Support
{
	#region CLASS Steward
	/// <summary>
	/// Hlavní správce podkladových dat aplikace.
	/// Používá se pro získání dat trvalého charakteru i pro volání systémových metod.
	/// </summary>
	/// <remarks>
	/// Obsahuje privátní singleton na svoji instanci, v níž uchovává trvalá data.
	/// Uchovává v sobě konfiguraci, reflexi výkonných tříd.
	/// </remarks>
	public class Steward
	{
		#region STATIC METODY
		/// <summary>
		/// Povinná metoda. kdo ji nezavolá, ten nepojede.
		/// V parametrech je třeba předat soupis typů služeb, které chceme, aby steward registroval a uměl obsloužit (najít a vracet).
		/// </summary>
		/// <param name="findServiceTypes">Soupis služeb, které budeme používat (které chceme v rámci aplikace detekovat)</param>
		public static void AppInit(Type[] findServiceTypes)
		{
			WinReg.SetRootDefaultFolder(@"Software\DJsoft\WinApsClient");
			Current._ServicesLoad(findServiceTypes);
		}
		/// <summary>
		/// Povinná metoda. Je třeba ji zavolat před koncem aplikace.
		/// </summary>
		public static void AppDone()
		{
			Current._ServicesUnLoad();
		}
		/// <summary>
		/// Vrátí soupis TYPŮ poskytovatelů služeb daného druhu.
		/// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
		/// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
		/// Výstupem je soupis typů konkrétních poskytovatelů této služby.
		/// </summary>
		/// <param name="service">Typ služby, například typeof(IGraphElementPainter)</param>
		/// <returns></returns>
		public static IEnumerable<Type> GetServiceTypeList(Type service)
		{
			return Current._GetServiceTypeList(service);
		}
		/// <summary>
		/// Vrátí soupis SKUTEČNÝCH OBJEKTŮ poskytovatelů služeb daného druhu.
		/// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
		/// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
		/// Výstupem je soupis objektů konkrétních poskytovatelů této služby.
		/// </summary>
		/// <typeparam name="S">Typ služby, například IGraphElementPainter</typeparam>
		/// <param name="cached">Příznak, zda je možno číst objekty z cache</param>
		/// <returns>Soupis objektů</returns>
		public static IEnumerable<S> GetServiceObjectList<S>(bool cached)
		{
			return Current._GetServiceObjectList<S>(cached);
		}
		/// <summary>
		/// Vrátí objekt jednoho konkrétního poskytovatele služby daného druhu.
		/// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
		/// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
		/// Výstupem je soupis objektů konkrétních poskytovatelů této služby.
		/// </summary>
		/// <typeparam name="S">Typ služby, například IGraphElementPainter</typeparam>
		/// <param name="providerType">Typ konkrétního poskytovatele služby, například typeof(PainterGanttRectangleCls), či spíš typ načtený z metody GetServiceTypeList()</param>
		/// <param name="cached">Příznak, zda je možno číst objekt z cache</param>
		/// <returns>Konkrétní poskytovatel anebo null</returns>
		public static S GetServiceObject<S>(Type providerType, bool cached)
		{
			return Current._GetServiceObject<S>(providerType, cached);
		}
		#endregion
		#region STATIC SLUŽBY
		/// <summary>
		/// Metoda zjistí, zda daný typ (dataType) implementuje daný interface.
		/// Oba typy jsou předané jako proměnné, to je důvod existence této metody.
		/// </summary>
		/// <param name="dataType">Typ testované třídy</param>
		/// <param name="interfaceType">Typ interface, které hledáme</param>
		/// <returns>true: testovaný typ implementuje daný interface</returns>
		public static bool TestTypeImplementInterface(Type dataType, Type interfaceType)
		{
			Type[] inf = dataType.FindInterfaces(delegate(Type t, object data) { return (t.FullName == ((Type)data).FullName); }, interfaceType);
			return (inf.Length > 0);
		}
		#endregion
		#region STATIC PROPERTY
		/// <summary>
		/// Obsahuje informaci o aktuálně přihlášeném uživateli.
		/// </summary>
		public static Noris.Schedule.Support.DataFace.NorisUserCls CurrentUser { get { return Current._CurrentUser; } }
		/// <summary>
		/// Obsahuje aktuální aplikační konfiguraci.
		/// </summary>
		public static AppConfigCls AppConfig { get { return Current._AppConfig; } }
		#endregion
		#region SINGLETON na svoji instanci
		/// <summary>
		/// Privátní přístup k instančním datům stewarda
		/// </summary>
		private static Steward Current
		{
			get
			{
				if (_Current != null) return _Current;
				lock (_LockerWrite)
				{
					if (_Current == null)
					{
						_Current = Steward.Create();
					}
				}
				return _Current;
			}
		}
		/// <summary>
		/// Statické úložiště pro instanci
		/// </summary>
		private static Steward _Current;
		/// <summary>
		/// Locker
		/// </summary>
		private static object _LockerWrite = new object();
		#endregion
		#region TVORBA INSTANCE, TVORBA INSTANČNÍCH PROMĚNNÝCH
		/// <summary>
		/// Konstruktor je private, zvenku si stewarda nikdo nevyrobí
		/// </summary>
		private Steward() { }
		/// <summary>
		/// Privátní tvorba stevarda
		/// </summary>
		private static Steward Create()
		{
			Steward steward = new Steward();

			steward._CurrentUser = new Noris.Schedule.Support.DataFace.NorisUserCls(false);
			steward._AppConfig = new AppConfigCls();
			steward._ServiceMaps = new Dictionary<Type, Dictionary<Type, object>>();
			steward._ServiceWriteLock = new object();

			return steward;
		}
		#endregion
		#region INSTANČNÍ PROMĚNNÉ
		/// <summary>
		/// Úložiště dat o aktuálně přihlášeném uživateli.
		/// </summary>
		private Noris.Schedule.Support.DataFace.NorisUserCls _CurrentUser;
		/// <summary>
		/// Aplikační konfigurace
		/// </summary>
		private AppConfigCls _AppConfig;
		/// <summary>
		/// Jednotné úložiště reflektovaných typů instalovaných služeb.
		/// Obsahuje :
		/// Key = typ služby (reprezentovaný typem interface)
		/// Value = seznam typů, které tuto službu poskytují 
		/// Seznam typů: Key = typ fyzické implementace, Value = cache instance služby nebo null.
		/// </summary>
		private Dictionary<Type, Dictionary<Type, object>> _ServiceMaps;
		/// <summary>
		/// Zámek na zápisy do _ServiceMaps
		/// </summary>
		private object _ServiceWriteLock;
		#endregion
		#region PRIVÁTNÍ INSTANČNÍ METODY
		#region SERVICES (System.Reflection): Load, Get, Activate, UnLoad
		/// <summary>
		/// Pomocí reflexe načte seznamy datových typů, které mohou sloužit jako Services (funkce, extendery, paintery, a kdovíco dalšího)
		/// </summary>
		private void _ServicesLoad(Type[] findServiceTypes)
		{
			// Příprava:
			lock (this._ServiceWriteLock)
			{
				this._ServiceMaps = new Dictionary<Type, Dictionary<Type, object>>();
				this._ServicesLoadAdd(findServiceTypes);
			}
		}
		/// <summary>
		/// Pomocí reflexe doplní existující seznam this._ServiceMaps o další služby a jejich poskytovatele.
		/// Zajistí načtení (reflexí fyzických knihoven) těch služeb, které jsou požadované, a přitom dosud nejsou načtené (ty už nedohledává).
		/// </summary>
		private void _ServicesLoadAdd(IEnumerable<Type> findServiceTypes)
		{
			Hashtable findTypes = new Hashtable();                               // Pomocný slovník, který si pro každý typ uloží příznak, zda typ implementuje dané interface
			foreach (Type service in findServiceTypes)
			{
				if (this._ServiceMaps.ContainsKey(service)) continue;            // Nonduplicitně!
				this._ServiceMaps.Add(service, new Dictionary<Type, object>());
				findTypes.Add(service, null);                                    // Pro QuickSearch v anonymním delegátovi
			}

			// Následující smyčka a rychlost:
			// Načtení mapy typů ze dvou knihoven (Schedule a Support) s celkovým počtem typů = 500 trvá 50 milisekund.
			IEnumerable<FileInfo> assemblyFiles = this._FindAssemblyFiles();
			foreach (FileInfo fileInfo in assemblyFiles)
			{
				Assembly assembly = Assembly.LoadFile(fileInfo.FullName);
				Type[] assemblyTypes = assembly.GetTypes();                      // Soupis všech typů (třídy, struktury, interface, enumy, atd atd)
				foreach (Type dataType in assemblyTypes)
				{
					if (!dataType.IsClass) continue;                             // Pokud nalezený typ není class, jdi na další. Testujeme jen třídy.

					// Najdu všechny interface (do foundTypes), které daný datový typ implementuje:
					Type[] foundTypes = dataType.FindInterfaces(
						delegate(Type t, object data)
						{	// Anonymní delegát dostává postupně všechny interface, které datový typ implementuje.
							// Má vrátit true, pokud máme konkrétní interface zařadit do výstupu:
							return (((Hashtable)data).ContainsKey(t));
						}, findTypes);

					// V poli foundTypes mám typy = interface, které implementuje datový typ, a na které jsme se ptali.
					foreach (Type implementedInterface in foundTypes)
						this._ServiceMaps[implementedInterface].Add(dataType, null);
				}
			}
		}
		/// <summary>
		/// Vrátí seznam souborů (FileInfo) DLL a EXE v adresáři programu aplikace.
		/// </summary>
		/// <returns></returns>
		private IEnumerable<FileInfo> _FindAssemblyFiles()
		{
			Assembly assembly = Assembly.GetExecutingAssembly();
			string path = Path.GetDirectoryName(assembly.Location);
			DirectoryInfo dirInfo = new DirectoryInfo(path);
			FileInfo[] files = dirInfo.GetFiles("*.*");

			List<FileInfo> result = new List<FileInfo>();
			foreach (FileInfo fileInfo in files)
			{
				string ext = fileInfo.Extension.ToLower();
				if (ext == ".dll" || ext == ".exe")
					result.Add(fileInfo);
			}
			return result;
		}
		/// <summary>
		/// Najde a vrátí soupis TYPŮ poskytovatelů dané služby.
		/// Pokud požadovaná služba není v interním seznamu (nebyla přítomna při inicializaci),
		/// dohledá její poskytovatele nyní. Ztráta je jen časová. Příště už budou načteny i data pro tuto novou službu.
		/// </summary>
		/// <param name="service"></param>
		/// <returns></returns>
		private IEnumerable<Type> _GetServiceTypeList(Type service)
		{
			Dictionary<Type, object> providers = _GetServiceProviders(service);
			if (providers == null) return null;
			return new List<Type>(providers.Keys);
		}
		/// <summary>
		/// Najde a vrátí soupis INSTANCÍ poskytovatelů dané služby.
		/// Vrací soupis objektů, které jsou vytvořeny z typu konkrétního poskytovatele.
		/// Pokud požadovaná služba není v interním seznamu (nebyla přítomna při inicializaci),
		/// dohledá její poskytovatele nyní. Ztráta je jen časová. Příště už budou načteny i data pro tuto novou službu.
		/// </summary>
		/// <typeparam name="S">Typ služby = typ interface, který má služba implementovat. Vrací objekty typované tímto typem.</typeparam>
		/// <param name="cached">true = umožní číst objekty z cache / false = chce vždy čerstvé objekty. Pozor, čas!!!</param>
		/// <returns></returns>
		private IEnumerable<S> _GetServiceObjectList<S>(bool cached)
		{
			Type service = typeof(S);                      // Typ služby
			List<S> result = new List<S>();                // Výstup

			Dictionary<Type, object> providers = _GetServiceProviders(service);
			if (providers == null) return null;
			IEnumerable<Type> types = new List<Type>(providers.Keys);
			foreach (Type type in types)
			{
				if (cached)
				{	// Objekty z cache (anebo nové do cache a pak z cache ven):
					// Poznámka: v cache (providers) vždy existuje položka pro klíč (type), 
					//     protože seznam typů (types) jsme načetli z tohoto soupisu. 
					//     Důvod: Dictionary nelze procházet cyklem foreach() a přitom do ní vepisovat nová data.
					if (providers[type] == null)
						providers[type] = ObjectGenerator<S>(type);
					result.Add((S)providers[type]);
				}
				else
				{	// Objekty do výstupu vždy nové:
					result.Add(ObjectGenerator<S>(type));
				}
			}
			return result;
		}
		/// <summary>
		/// Vrátí objekt jednoho konkrétního poskytovatele služby daného druhu.
		/// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
		/// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
		/// Výstupem je soupis objektů konkrétních poskytovatelů této služby.
		/// </summary>
		/// <typeparam name="S">Typ služby, například IGraphElementPainter</typeparam>
		/// <param name="providerType">Typ konkrétního poskytovatele služby, například typeof(PainterGanttRectangleCls), či spíš typ načtený z metody GetServiceTypeList()</param>
		/// <param name="cached">Příznak, zda je možno číst objekt z cache</param>
		/// <returns>Konkrétní poskytovatel anebo null</returns>
		private S _GetServiceObject<S>(Type providerType, bool cached)
		{
			Type service = typeof(S);                      // Typ služby

			// Najdu seznam služeb požadovaného typu S: (jde o soupis typů a cachovaných objektů providerů):
			Dictionary<Type, object> providers = _GetServiceProviders(service);
			if (providers == null) return default(S);

			// Najdu konkrétního providera, pokud nebude v seznamu tak ho ani nebudu vytvářet, nejspíš jde o jiný typ než je přípustné:
			object provider = null;
			if (!providers.TryGetValue(providerType, out provider)) return default(S);

			// Z cache / do cache anebo nový objekt:
			if (cached)
			{	// Objekty z cache (anebo nové do cache a pak z cache ven):
				// Poznámka: v cache (providers) vždy existuje položka pro klíč (type), 
				//     protože seznam typů (types) jsme načetli z tohoto soupisu. 
				//     Důvod: Dictionary nelze procházet cyklem foreach() a přitom do ní vepisovat nová data.
				if (provider == null)
				{
					provider = ObjectGenerator<S>(providerType);
					providers[providerType] = provider;
				}
			}
			else
			{	// Objekty do výstupu vždy nové:
				provider = ObjectGenerator<S>(providerType);
			}
			return (S)provider;
		}
		/// <summary>
		/// Metoda vrátí vnitřní dictionary pro danou službu.
		/// Pokud služba není načtena, načte ji.
		/// Vrací živou referenci do interní paměti.
		/// </summary>
		/// <param name="service"></param>
		/// <returns></returns>
		private Dictionary<Type, object> _GetServiceProviders(Type service)
		{
			Dictionary<Type, object> providers;
			// Máme už načtené providery dané služby:
			if (this._ServiceMaps.TryGetValue(service, out providers)) return providers;
			
			// Nemáme => můžeme služby zamknout, zkusit je donačíst, atd:
			lock (this._ServiceWriteLock)
			{
				if (this._ServiceMaps.TryGetValue(service, out providers)) return providers;
				this._ServicesLoadAdd(new Type[] { service });
				if (this._ServiceMaps.TryGetValue(service, out providers)) return providers;
			}

			return null;
		}
		/// <summary>
		/// Unload služeb.
		/// Uvolní služby z cache. Uvolní cache.
		/// Pokud je služba IDisposable, zavolá její Dispose.
		/// </summary>
		private void _ServicesUnLoad()
		{
			lock (this._ServiceWriteLock)
			{
				// Provedeme Dispose na všechny objekty, které to mají rády (ale na každém jen jedenkrát):
				List<object> disposedProviders = new List<object>();     // Reference na objekty, pro které jsme už zavolali Dispose
				foreach (Dictionary<Type, object> providers in this._ServiceMaps.Values)
				{
					foreach (object provider in providers)
					{
						if (provider == null) continue;                  // Pro tento typ nebyl provider cachován. Jdeme na další.
						if (provider is IDisposable)
						{	// Pokud je provider (krom jiného) i IDisposable:
							bool wasDisposed = disposedProviders.Exists(delegate(object w) { return (Object.ReferenceEquals(w, provider)); });
							if (!wasDisposed)
							{	// A pokud ještě na tomto konkrétním objektu nebyl proveden Dispose:
								disposedProviders.Add(provider);
								((IDisposable)provider).Dispose();
							}
						}
					}
				}

				// Odebereme vše z paměti, o zbytek se postará GC:
				this._ServiceMaps.Clear();
			}
		}
		/// <summary>
		/// Vrátí nově vytvořený objekt daného typu.
		/// Pokud se nepodaří, vrátí null.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="type"></param>
		/// <returns></returns>
		public static T ObjectGenerator<T>(Type type)
		{
			T result = default (T);
			try
			{
				object obj = System.Activator.CreateInstance(type, false);
				result = (T)obj;
			}
			catch
			{
				result = default(T);
			}
			return result;
		}
		/// <summary>
		/// Vrátí seznam objektů
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="typeList"></param>
		/// <returns></returns>
		public static List<T> ActivateTypes<T>(IEnumerable<Type> typeList)
		{
			List<T> result = new List<T>();
			foreach (Type type in typeList)
				result.Add((T)System.Activator.CreateInstance(type, false));
			return result;
		}
		#endregion
		#endregion
	}
	#endregion
	#region CLASS AppConfigCls : registr konfigurace programu
	/// <summary>
	/// Konfigurace aplikace, data čtená z/do registru
	/// </summary>
	public class AppConfigCls
	{
		#region DIRECTORIES
		/// <summary>
		/// Vrací cestu k lokálnímu adresáři, kde si aplikace může ukládat svoje data.
		/// Typicky je to adresář (System:Documents and Settings\All Users)\Data aplikací\NorisAps
		/// </summary>
		public string AppDataRootLocalPath
		{
			get
			{
				string dirName = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "NorisAps");
				_CreateDirectoryIfNotExists(dirName);
				return dirName;
			}
		}
		/// <summary>
		/// Vrací cestu k lokálnímu adresáři, kde si aplikace může ukládat svoje data.
		/// Typicky je to adresář (System:Documents and Settings\All Users)\Data aplikací\NorisAps\IconCache
		/// </summary>
		public string AppDataIconCacheLocalPath
		{
			get
			{
				string dirName = System.IO.Path.Combine(AppDataRootLocalPath, "IconCache");
				_CreateDirectoryIfNotExists(dirName);
				return dirName;
			}
		}
		/// <summary>
		/// Zkontroluje existenci adresář (parametr), a pokud neexistuje pak jej vytvoří.
		/// </summary>
		/// <param name="dirName"></param>
		private static void _CreateDirectoryIfNotExists(string dirName)
		{
			if (!System.IO.Directory.Exists(dirName))
				System.IO.Directory.CreateDirectory(dirName);
		}
		#endregion
		#region PRIVÁTNÍ PROMĚNNÉ
		private int _CurrentProfileId;
		private bool _CurrentProfileIdReaded;
		private ConnectProfile _CurrentProfile;
		private bool _CurrentProfileReaded;
		#endregion
		#region ConnectInfo
		/// <summary>
		/// Connect string do databáze
		/// </summary>
		public string ConnectString { get { return CurrentProfile.ProfileConnString; } set { CurrentProfile.ProfileConnString = value; } }
		/// <summary>
		/// ID profilu, který je nyní vybrán. Načítá/ukládá se z/do WinReg.
		/// </summary>
		public int CurrentProfileId
		{
			get
			{
				if (!this._CurrentProfileIdReaded)
				{
					this._CurrentProfileId = WinReg.ReadInt32(_REGFOLDER, "CurrentProfileID", 0);
					if (this._CurrentProfileId <= 0)
					{
						this._CurrentProfileId = 1;
						WinReg.WriteInt32(_REGFOLDER, "CurrentProfileID", this._CurrentProfileId);
					}
					this._CurrentProfileIdReaded = true;
				}
				return this._CurrentProfileId;
			}
			set
			{
				this._CurrentProfileId = value;
				this._CurrentProfileIdReaded = true;
				WinReg.WriteInt32(_REGFOLDER, "CurrentProfileID", this._CurrentProfileId);
				this.CurrentProfile = ConnectProfile.Load(value);
			}
		}
		/// <summary>
		/// Data aktuálního profilu
		/// </summary>
		public ConnectProfile CurrentProfile
		{
			get
			{
				if (!this._CurrentProfileReaded)
				{
					int profileId = this.CurrentProfileId;    // Načte z WinReg číslo ID profilu
					this._CurrentProfile = ConnectProfile.Load(profileId);
					this._CurrentProfileReaded = true;
				}
				return this._CurrentProfile;
			}
			set
			{
				this._CurrentProfile = value;
				if (this._CurrentProfile.ProfileID != this._CurrentProfileId)
					this._CurrentProfileId = this._CurrentProfile.ProfileID;
			}
		}
		#endregion
		#region CONSTANTS
		private const string _REGFOLDER = "Config";
		#endregion
	}
	#endregion
	#region CLASS ConnectProfile : jednotlivá položka profilu připojení k databázi
	/// <summary>
	/// Jeden profil připojení k databázi (ID, jméno, ConnectString)
	/// </summary>
	public class ConnectProfile
	{
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="profileId"></param>
		/// <param name="name"></param>
		/// <param name="connect"></param>
		public ConnectProfile(int profileId, string name, string connect)
		{
			_ProfileID = profileId;
			_ProfileName = name;
			_ProfileConnString = connect;
		}
		/// <summary>
		/// ID profilu
		/// </summary>
		public int ProfileID { get { return _ProfileID; } }
		private int _ProfileID;
		/// <summary>
		/// Jméno profilu
		/// </summary>
		public string ProfileName { get { return _ProfileName; } set { _ProfileName = value; } }
		private string _ProfileName;
		/// <summary>
		/// Connect string
		/// </summary>
		public string ProfileConnString { get { return _ProfileConnString; } set { _ProfileConnString = value; } }
		private string _ProfileConnString;
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{" + this.ProfileID.ToString() + ": " + this.ProfileName + "}";
		}
		/// <summary>
		/// Vrátí instanci s načtenými daty z WinReg
		/// </summary>
		/// <param name="profileId"></param>
		public static ConnectProfile Load(int profileId)
		{
			string profileFolder = "Config\\Profiles";
			string profileNameReg = "Profile" + profileId.ToString("D4") + "Name";
			string profileNameCon = "Profile" + profileId.ToString("D4") + "Data";
			string name = WinReg.ReadString(profileFolder, profileNameReg, "");
			string conn = WinReg.ReadStringCrypt(profileFolder, profileNameCon, "");
			ConnectProfile result = new ConnectProfile(profileId, name, conn);
			if (result.ProfileName == "")
			{
				result.ProfileName = "Výchozí profil";
				result.Save();
			}
			return result;
		}
		/// <summary>
		/// Uloží svoje instanční data do WinReg
		/// </summary>
		/// <param name="profileId"></param>
		public void Save()
		{
			string profileFolder = "Config\\Profiles";
			string profileNameReg = "Profile" + this.ProfileID.ToString("D4") + "Name";
			string profileNameCon = "Profile" + this.ProfileID.ToString("D4") + "Data";
			WinReg.WriteString(profileFolder, profileNameReg, this.ProfileName);
			WinReg.WriteStringCrypt(profileFolder, profileNameCon, this.ProfileConnString);
		}
	}
	#endregion
}
